/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package exemples.gp;

import java.util.Random;
import org.jgap.InvalidConfigurationException;
import org.jgap.gp.CommandGene;
import org.jgap.gp.GPFitnessFunction;
import org.jgap.gp.GPProblem;
import org.jgap.gp.IGPProgram;
import org.jgap.gp.function.*;
import org.jgap.gp.impl.DefaultGPFitnessEvaluator;
import org.jgap.gp.impl.DeltaGPFitnessEvaluator;
import org.jgap.gp.impl.GPConfiguration;
import org.jgap.gp.impl.GPGenotype;
import org.jgap.gp.terminal.Terminal;
import org.jgap.gp.terminal.Variable;

/**
 *
 * @author Heitor
 */
public class Exemplo extends GPProblem {

    public Exemplo(GPConfiguration a_conf) throws InvalidConfigurationException {
        super(a_conf);
    }
    public static Variable vx;
    public static Float[] x = new Float[20];
    public static Float[] y = new Float[20];

    @Override
    public GPGenotype create() throws InvalidConfigurationException {

        GPConfiguration conf = getGPConfiguration();

        Class[] types = {
            CommandGene.FloatClass};
        Class[][] argTypes = {
            {}
        };

        //Determina as funções e terminais usados para criar a população inicial
        // ----------------------------------------------------------------
        CommandGene[][] nodeSets = {
            {
                vx = Variable.create(conf, "X", CommandGene.FloatClass),
                new Add(conf, CommandGene.FloatClass),
                new Subtract(conf, CommandGene.FloatClass),
                new Multiply(conf, CommandGene.FloatClass),
                new Divide(conf, CommandGene.FloatClass),
                new Pow(conf, CommandGene.FloatClass),
                // Terminal com o valor entre 2 e 10.
                // O true significa somete valores inteiros.
                new Terminal(conf, CommandGene.FloatClass, 2.0d, 10.0d, true),}
        };

        Random random = new Random();
        // Inicia aleatoriamente as variaveis x e y para a expressão x^3+x^2-x.
        // As variaveis x e y são 2 arrays de 20 posições, logo 20 fitness cases.
        // ----------------------------------------------------------------
        for (int i = 0; i < 20; i++) {
            float f = 2.0f * (random.nextFloat() - 0.5f);
            x[i] = new Float(f);
            y[i] = (f * f * f) + (f * f) - f;
            System.out.println(i + ") " + x[i] + " " + y[i]);
        }

        // Cria o genótipo com a população inicial
        // 100 significa que um indivíduo pode ter no max 100 nós.
        // ----------------------------------------
        return GPGenotype.randomInitialGenotype(conf, types, argTypes, nodeSets, 100, true);


    }

    public static void main(String[] args) throws InvalidConfigurationException {

        // Configura os parametros do algoritmo.
        // ---------------------------------
        GPConfiguration config = new GPConfiguration();
        //determina que o menor valor de fitness é o melhor.
        config.setGPFitnessEvaluator(new DefaultGPFitnessEvaluator());
        //profundidade máxima da arvore dos indivíduos da pop inicial.
        config.setMaxInitDepth(5);
        //porcentagem da população gerada por cruzamento.
        config.setCrossoverProb(0.9f);
        //porcentagem da população gerada por reprodução.
        config.setReproductionProb(0.1f);
        //probabilidade de ocorrer mutação.
        config.setMutationProb(0.01f);
        //tamanho da população
        config.setPopulationSize(100);
        //função fitness usada
        config.setFitnessFunction(new Exemplo.FitnessFunction());

        //Instância a classe que representa o problema, passando
        //a configuração da PG como parâmetro.
        GPProblem prob = new Exemplo(config);
        //define as caracteristicas do indivíduos e cria a população inicial.
        GPGenotype gp = prob.create();
        //evolui 100 populações
        gp.evolve(1000);
        //exibe a melhor solução
        gp.outputSolution(gp.getAllTimeBest());

    }//end main

    public static class FitnessFunction
            extends GPFitnessFunction {

        @Override
        protected double evaluate(final IGPProgram a_subject) {
            return computeRawFitness(a_subject);
        }

        public double computeRawFitness(final IGPProgram ind) {
            double error = 0.0f;
            Object[] noargs = new Object[0];
            // Evaluate function for input numbers 0 to 20.
            // --------------------------------------------
            for (int i = 0; i < 20; i++) {
                // Provide the variable X with the input number.
                // See method create(), declaration of "nodeSets" for where X is
                // defined.
                // -------------------------------------------------------------
                vx.set(x[i]);
                try {
                    // Execute the GP program representing the function to be evolved.
                    // As in method create(), the return type is declared as float (see
                    // declaration of array "types").
                    // ----------------------------------------------------------------
                    double result = ind.execute_float(0, noargs);
                    // Sum up the error between actual and expected result to get a defect
                    // rate.
                    // -------------------------------------------------------------------
                    error += Math.abs(result - y[i]);
                    // If the error is too high, stop evlauation and return worst error
                    // possible.
                    // ----------------------------------------------------------------
                    if (Double.isInfinite(error)) {
                        return Double.MAX_VALUE;
                    }
                } catch (ArithmeticException ex) {
                    // This should not happen, some illegal operation was executed.
                    // ------------------------------------------------------------
                    System.out.println("x = " + x[i].floatValue());
                    System.out.println(ind);
                    throw ex;
                }
            }
            // In case the error is small enough, consider it perfect.
            // -------------------------------------------------------
            if (error < 0.001) {
                error = 0.0d;
            }
            return error;
        }
    }
}